﻿using System;
using System.Net;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Reflection;
using System.Linq.Expressions;
using System.Linq;
using System.Globalization;
using System.Collections.Generic;

namespace Fayshine.VA.Helpers
{
    public static class LinqHelper
    {
        #region Fields
        private static readonly ConstantExpression nullLiteral;
        private static readonly Dictionary<Type, IEnumerable<Type>> supportedConversions;
        private static readonly Dictionary<Type, IEnumerable<FilterOperation>> supportedOperators;

        #endregion

        #region Constructors
        static LinqHelper()
        {
            nullLiteral = Expression.Constant(null);
            Dictionary<Type, IEnumerable<Type>> dictionary = new Dictionary<Type, IEnumerable<Type>>();
            dictionary.Add(typeof(byte), new Type[] { typeof(decimal), typeof(double), typeof(float), typeof(ulong), typeof(long), typeof(uint), typeof(int), typeof(ushort), typeof(short) });
            dictionary.Add(typeof(ushort), new Type[] { typeof(decimal), typeof(double), typeof(float), typeof(ulong), typeof(long), typeof(uint), typeof(int), typeof(short) });
            dictionary.Add(typeof(uint), new Type[] { typeof(decimal), typeof(double), typeof(float), typeof(ulong), typeof(long) });
            dictionary.Add(typeof(ulong), new Type[] { typeof(decimal), typeof(double), typeof(float) });
            dictionary.Add(typeof(sbyte), new Type[] { typeof(decimal), typeof(double), typeof(float), typeof(long), typeof(int), typeof(short) });
            dictionary.Add(typeof(short), new Type[] { typeof(decimal), typeof(double), typeof(float), typeof(long), typeof(int) });
            dictionary.Add(typeof(int), new Type[] { typeof(decimal), typeof(double), typeof(float), typeof(long) });
            dictionary.Add(typeof(long), new Type[] { typeof(decimal), typeof(double), typeof(float) });
            dictionary.Add(typeof(float), new Type[] { typeof(double) });
            supportedConversions = dictionary;
            Dictionary<Type, IEnumerable<FilterOperation>> dictionary2 = new Dictionary<Type, IEnumerable<FilterOperation>>();
            FilterOperation[] operatorArray = new FilterOperation[6];
            operatorArray[1] = FilterOperation.IsLessThanOrEqualTo;
            operatorArray[2] = FilterOperation.IsEqualTo;
            operatorArray[3] = FilterOperation.IsNotEqualTo;
            operatorArray[4] = FilterOperation.IsGreaterThanOrEqualTo;
            operatorArray[5] = FilterOperation.IsGreaterThan;
            dictionary2.Add(typeof(IComparable), operatorArray);
            FilterOperation[] operatorArray2 = new FilterOperation[10];
            operatorArray2[1] = FilterOperation.IsLessThanOrEqualTo;
            operatorArray2[2] = FilterOperation.IsEqualTo;
            operatorArray2[3] = FilterOperation.IsNotEqualTo;
            operatorArray2[4] = FilterOperation.IsGreaterThanOrEqualTo;
            operatorArray2[5] = FilterOperation.IsGreaterThan;
            operatorArray2[6] = FilterOperation.StartsWith;
            operatorArray2[7] = FilterOperation.EndsWith;
            operatorArray2[8] = FilterOperation.Contains;
            operatorArray2[9] = FilterOperation.IsContainedIn;
            dictionary2.Add(typeof(string), operatorArray2);
            dictionary2.Add(typeof(Uri), new FilterOperation[] { FilterOperation.IsEqualTo, FilterOperation.IsNotEqualTo });
            dictionary2.Add(typeof(bool?), new FilterOperation[] { FilterOperation.IsEqualTo, FilterOperation.IsNotEqualTo });
            FilterOperation[] operatorArray5 = new FilterOperation[6];
            operatorArray5[1] = FilterOperation.IsLessThanOrEqualTo;
            operatorArray5[2] = FilterOperation.IsEqualTo;
            operatorArray5[3] = FilterOperation.IsNotEqualTo;
            operatorArray5[4] = FilterOperation.IsGreaterThanOrEqualTo;
            operatorArray5[5] = FilterOperation.IsGreaterThan;
            dictionary2.Add(typeof(byte?), operatorArray5);
            FilterOperation[] operatorArray6 = new FilterOperation[6];
            operatorArray6[1] = FilterOperation.IsLessThanOrEqualTo;
            operatorArray6[2] = FilterOperation.IsEqualTo;
            operatorArray6[3] = FilterOperation.IsNotEqualTo;
            operatorArray6[4] = FilterOperation.IsGreaterThanOrEqualTo;
            operatorArray6[5] = FilterOperation.IsGreaterThan;
            dictionary2.Add(typeof(sbyte?), operatorArray6);
            FilterOperation[] operatorArray7 = new FilterOperation[6];
            operatorArray7[1] = FilterOperation.IsLessThanOrEqualTo;
            operatorArray7[2] = FilterOperation.IsEqualTo;
            operatorArray7[3] = FilterOperation.IsNotEqualTo;
            operatorArray7[4] = FilterOperation.IsGreaterThanOrEqualTo;
            operatorArray7[5] = FilterOperation.IsGreaterThan;
            dictionary2.Add(typeof(short?), operatorArray7);
            FilterOperation[] operatorArray8 = new FilterOperation[6];
            operatorArray8[1] = FilterOperation.IsLessThanOrEqualTo;
            operatorArray8[2] = FilterOperation.IsEqualTo;
            operatorArray8[3] = FilterOperation.IsNotEqualTo;
            operatorArray8[4] = FilterOperation.IsGreaterThanOrEqualTo;
            operatorArray8[5] = FilterOperation.IsGreaterThan;
            dictionary2.Add(typeof(ushort?), operatorArray8);
            FilterOperation[] operatorArray9 = new FilterOperation[6];
            operatorArray9[1] = FilterOperation.IsLessThanOrEqualTo;
            operatorArray9[2] = FilterOperation.IsEqualTo;
            operatorArray9[3] = FilterOperation.IsNotEqualTo;
            operatorArray9[4] = FilterOperation.IsGreaterThanOrEqualTo;
            operatorArray9[5] = FilterOperation.IsGreaterThan;
            dictionary2.Add(typeof(int?), operatorArray9);
            FilterOperation[] operatorArray10 = new FilterOperation[6];
            operatorArray10[1] = FilterOperation.IsLessThanOrEqualTo;
            operatorArray10[2] = FilterOperation.IsEqualTo;
            operatorArray10[3] = FilterOperation.IsNotEqualTo;
            operatorArray10[4] = FilterOperation.IsGreaterThanOrEqualTo;
            operatorArray10[5] = FilterOperation.IsGreaterThan;
            dictionary2.Add(typeof(uint?), operatorArray10);
            FilterOperation[] operatorArray11 = new FilterOperation[6];
            operatorArray11[1] = FilterOperation.IsLessThanOrEqualTo;
            operatorArray11[2] = FilterOperation.IsEqualTo;
            operatorArray11[3] = FilterOperation.IsNotEqualTo;
            operatorArray11[4] = FilterOperation.IsGreaterThanOrEqualTo;
            operatorArray11[5] = FilterOperation.IsGreaterThan;
            dictionary2.Add(typeof(long?), operatorArray11);
            FilterOperation[] operatorArray12 = new FilterOperation[6];
            operatorArray12[1] = FilterOperation.IsLessThanOrEqualTo;
            operatorArray12[2] = FilterOperation.IsEqualTo;
            operatorArray12[3] = FilterOperation.IsNotEqualTo;
            operatorArray12[4] = FilterOperation.IsGreaterThanOrEqualTo;
            operatorArray12[5] = FilterOperation.IsGreaterThan;
            dictionary2.Add(typeof(ulong?), operatorArray12);
            FilterOperation[] operatorArray13 = new FilterOperation[6];
            operatorArray13[1] = FilterOperation.IsLessThanOrEqualTo;
            operatorArray13[2] = FilterOperation.IsEqualTo;
            operatorArray13[3] = FilterOperation.IsNotEqualTo;
            operatorArray13[4] = FilterOperation.IsGreaterThanOrEqualTo;
            operatorArray13[5] = FilterOperation.IsGreaterThan;
            dictionary2.Add(typeof(float?), operatorArray13);
            FilterOperation[] operatorArray14 = new FilterOperation[6];
            operatorArray14[1] = FilterOperation.IsLessThanOrEqualTo;
            operatorArray14[2] = FilterOperation.IsEqualTo;
            operatorArray14[3] = FilterOperation.IsNotEqualTo;
            operatorArray14[4] = FilterOperation.IsGreaterThanOrEqualTo;
            operatorArray14[5] = FilterOperation.IsGreaterThan;
            dictionary2.Add(typeof(double?), operatorArray14);
            FilterOperation[] operatorArray15 = new FilterOperation[6];
            operatorArray15[1] = FilterOperation.IsLessThanOrEqualTo;
            operatorArray15[2] = FilterOperation.IsEqualTo;
            operatorArray15[3] = FilterOperation.IsNotEqualTo;
            operatorArray15[4] = FilterOperation.IsGreaterThanOrEqualTo;
            operatorArray15[5] = FilterOperation.IsGreaterThan;
            dictionary2.Add(typeof(decimal?), operatorArray15);
            FilterOperation[] operatorArray16 = new FilterOperation[6];
            operatorArray16[1] = FilterOperation.IsLessThanOrEqualTo;
            operatorArray16[2] = FilterOperation.IsEqualTo;
            operatorArray16[3] = FilterOperation.IsNotEqualTo;
            operatorArray16[4] = FilterOperation.IsGreaterThanOrEqualTo;
            operatorArray16[5] = FilterOperation.IsGreaterThan;
            dictionary2.Add(typeof(DateTime?), operatorArray16);
            FilterOperation[] operatorArray17 = new FilterOperation[6];
            operatorArray17[1] = FilterOperation.IsLessThanOrEqualTo;
            operatorArray17[2] = FilterOperation.IsEqualTo;
            operatorArray17[3] = FilterOperation.IsNotEqualTo;
            operatorArray17[4] = FilterOperation.IsGreaterThanOrEqualTo;
            operatorArray17[5] = FilterOperation.IsGreaterThan;
            dictionary2.Add(typeof(TimeSpan?), operatorArray17);
            FilterOperation[] operatorArray18 = new FilterOperation[6];
            operatorArray18[1] = FilterOperation.IsLessThanOrEqualTo;
            operatorArray18[2] = FilterOperation.IsEqualTo;
            operatorArray18[3] = FilterOperation.IsNotEqualTo;
            operatorArray18[4] = FilterOperation.IsGreaterThanOrEqualTo;
            operatorArray18[5] = FilterOperation.IsGreaterThan;
            dictionary2.Add(typeof(char?), operatorArray18);
            supportedOperators = dictionary2;
        }

        #endregion

        #region Public Methods
        public static Expression GetFilterExpression<T>(this SimpleFilterDescription filterDescriptor, ParameterExpression param)
        {
            Type type = typeof(T);
            object value = filterDescriptor.Value;
            PropertyInfo propertyInfo;
            Expression expression;
            Expression expression2;
            propertyInfo = type.GetProperty(filterDescriptor.PropertyPath);
            
            //Steven: if propertyInfo == null, get it from indexer 
            if (propertyInfo == null)
            {
                propertyInfo = GetPropertyOrIndexer(type, filterDescriptor.PropertyPath);
            }

            expression = GenerateProperty(type, filterDescriptor.PropertyPath, param);
            expression2 = GenerateConstant(value);
            FilterOperation filterOperator = filterDescriptor.Operator;

            if ((((filterOperator == FilterOperation.IsEqualTo)
                || (filterOperator == FilterOperation.IsNotEqualTo))
                && !expression.Type.IsValueType)
                && !expression2.Type.IsValueType)
            {
                if (expression.Type != expression2.Type)
                {
                    if (!expression.Type.IsAssignableFrom(expression2.Type))
                    {
                        expression = Expression.Convert(expression, expression2.Type);
                    }
                    else
                    {
                        expression2 = Expression.Convert(expression2, expression.Type);
                    }
                }
            }
            else if (expression.Type.IsEnum)
            {
                Type underlyingType = Enum.GetUnderlyingType(expression.Type.GetNonNullableType());
                bool flag2 = expression.Type.IsNullableType();
                if (flag2)
                {
                    underlyingType = typeof(Nullable<>).MakeGenericType(new Type[] { underlyingType });
                }
                if (expression2.Type != underlyingType)
                {
                    if ((value != null) && !flag2)
                    {
                        value = Convert.ChangeType(value, underlyingType, CultureInfo.InvariantCulture);
                        expression2 = GenerateConstant(value);
                    }
                    else
                    {
                        expression2 = Expression.Convert(expression2, underlyingType);
                    }
                }
                if (expression.Type != expression2.Type)
                {
                    Expression expression3 = PromoteExpression(expression, expression2.Type, true);
                    expression = expression3;
                }
            }
            else if (propertyInfo.PropertyType.IsNullableType() && (expression.Type != expression2.Type))
            {
                ConstantExpression expression4 = expression2 as ConstantExpression;
                if (expression4 != null)
                {
                    expression2 = Expression.Constant(expression4.Value, expression.Type);
                }
            }
            return BuildFilterExpression(expression, filterOperator, expression2, filterDescriptor.IsCaseSensitive);

        }
        public static Type GetNonNullableType(this Type type)
        {
            if (!IsNullableType(type))
            {
                return type;
            }
            return type.GetGenericArguments()[0];
        }
        public static bool IsNullableType(this Type type)
        {
            return (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Nullable<>)));
        }

        public static Expression GetFilterExpression<T>(this LogicalOperation filterOperator, ParameterExpression param, bool ignoreNull, params SimpleFilterDescription[] filterDescriptors)
        {
            return filterOperator.GetFilterExpression<T>(param, ignoreNull, (IEnumerable<SimpleFilterDescription>)filterDescriptors);
        }
        public static Expression GetFilterExpression<T>(this LogicalOperation filterOperator, ParameterExpression param, bool ignoreNull, IEnumerable<SimpleFilterDescription> filterDescriptors)
        {

            Expression filtersExpression = null;
            foreach (SimpleFilterDescription descriptor in filterDescriptors)
            {
                if (ignoreNull && descriptor.Value == null) continue;
                Expression right = descriptor.GetFilterExpression<T>(param);
                if (filtersExpression == null)
                {
                    filtersExpression = right;
                }
                else
                {
                    if (filterOperator == LogicalOperation.And)
                    {
                        filtersExpression = Expression.AndAlso(filtersExpression, right);
                    }
                    else
                    {
                        filtersExpression = Expression.OrElse(filtersExpression, right);
                    }
                }

            }
            return filtersExpression;
        }

        #endregion

        #region Pirvate Methods
        private static Expression PromoteExpression(Expression expr, Type type, bool exact)
        {
            if (expr.Type == type)
            {
                return expr;
            }
            ConstantExpression expression = expr as ConstantExpression;
            if (((expression != null) && (expression == nullLiteral)) && (!type.IsValueType || type.IsNullableType()))
            {
                return Expression.Constant(null, type);
            }
            if (!IsSupportedConversion(expr.Type, type))
            {
                return null;
            }
            if (!type.IsValueType && !exact)
            {
                return expr;
            }
            return Expression.Convert(expr, type);
        }
        private static bool IsSupportedConversion(Type source, Type target)
        {
            if (source == target)
            {
                return true;
            }
            if (!target.IsValueType)
            {
                return target.IsAssignableFrom(source);
            }
            Type nonNullableType = source.GetNonNullableType();
            Type enumType = target.GetNonNullableType();
            if ((nonNullableType != source) && (enumType == target))
            {
                return false;
            }
            if (nonNullableType.IsEnum)
            {
                nonNullableType = Enum.GetUnderlyingType(nonNullableType);
            }
            if (enumType.IsEnum)
            {
                enumType = Enum.GetUnderlyingType(enumType);
            }
            return ((nonNullableType == enumType) || (supportedConversions.ContainsKey(nonNullableType) && supportedConversions[nonNullableType].Contains<Type>(enumType)));
        }

        private static Expression BuildFilterExpression(Expression propertyExpression, FilterOperation filterOperator, Expression valueExpression, bool isCaseSensitive)
        {
            if ((propertyExpression.Type == typeof(string)) && !isCaseSensitive)
            {
                propertyExpression = GenerateToLowerCall(propertyExpression);
                if (valueExpression != nullLiteral)
                {
                    valueExpression = GenerateToLowerCall(valueExpression);
                }
            }
            switch (filterOperator)
            {
                case FilterOperation.IsLessThan:
                    return GenerateLessThan(propertyExpression, valueExpression);

                case FilterOperation.IsLessThanOrEqualTo:
                    return GenerateLessThanEqual(propertyExpression, valueExpression);

                case FilterOperation.IsEqualTo:
                    return GenerateEqual(propertyExpression, valueExpression);

                case FilterOperation.IsNotEqualTo:
                    return GenerateNotEqual(propertyExpression, valueExpression);

                case FilterOperation.IsGreaterThanOrEqualTo:
                    return GenerateGreaterThanEqual(propertyExpression, valueExpression);

                case FilterOperation.IsGreaterThan:
                    return GenerateGreaterThan(propertyExpression, valueExpression);

                case FilterOperation.StartsWith:
                    return GenerateStartsWith(propertyExpression, valueExpression);

                case FilterOperation.EndsWith:
                    return GenerateEndsWith(propertyExpression, valueExpression);

                case FilterOperation.Contains:
                    return GenerateContains(propertyExpression, valueExpression);

                case FilterOperation.IsContainedIn:
                    return GenerateIsContainedIn(propertyExpression, valueExpression);
            }
            return null;
        }
        private static Expression GenerateCompareMethod(Expression left, Expression right)
        {
            if (left.Type == typeof(string))
            {
                MethodInfo info = typeof(string).GetMethod("Compare", new Type[] { left.Type, right.Type });
                return Expression.Call(null, info, left, right);
            }
            MethodInfo method = left.Type.GetMethod("CompareTo", new Type[] { right.Type });
            return Expression.Call(left, method, new Expression[] { right });
        }
        private static ConstantExpression GenerateConstant(object value)
        {
            if (value == null)
            {
                return nullLiteral;
            }
            return Expression.Constant(value);
        }
        private static Expression GenerateContains(Expression left, Expression right)
        {
            return Expression.Equal(GenerateMethodCall("Contains", left, right), Expression.Constant(true));
        }
        private static Expression GenerateEndsWith(Expression left, Expression right)
        {
            return Expression.Equal(GenerateMethodCall("EndsWith", left, right), Expression.Constant(true));
        }
        private static Expression GenerateEqual(Expression left, Expression right)
        {
            if (UseCompareMethod(left, right))
            {
                return Expression.Equal(GenerateCompareMethod(left, right), Expression.Constant(0));
            }
            return Expression.Equal(left, right);
        }
        private static Expression GenerateGreaterThan(Expression left, Expression right)
        {
            if (UseCompareMethod(left, right))
            {
                return Expression.GreaterThan(GenerateCompareMethod(left, right), Expression.Constant(0));
            }
            return Expression.GreaterThan(left, right);
        }
        private static Expression GenerateGreaterThanEqual(Expression left, Expression right)
        {
            if (UseCompareMethod(left, right))
            {
                return Expression.GreaterThanOrEqual(GenerateCompareMethod(left, right), Expression.Constant(0));
            }
            return Expression.GreaterThanOrEqual(left, right);
        }
        private static Expression GenerateIsContainedIn(Expression left, Expression right)
        {
            return Expression.Equal(GenerateMethodCall("Contains", right, left), Expression.Constant(true));
        }
        private static Expression GenerateLessThan(Expression left, Expression right)
        {
            if (UseCompareMethod(left, right))
            {
                return Expression.LessThan(GenerateCompareMethod(left, right), Expression.Constant(0));
            }
            return Expression.LessThan(left, right);
        }
        private static Expression GenerateLessThanEqual(Expression left, Expression right)
        {
            if (UseCompareMethod(left, right))
            {
                return Expression.LessThanOrEqual(GenerateCompareMethod(left, right), Expression.Constant(0));
            }
            return Expression.LessThanOrEqual(left, right);
        }
        private static Expression GenerateMethodCall(string methodName, Expression left, Expression right)
        {
            return Expression.Call(left, GetMethod(methodName, left, right), new Expression[] { right });
        }
        private static MethodInfo GetMethod(string methodName, Expression left, Expression right)
        {
            return left.Type.GetMethod(methodName, new Type[] { right.Type });
        }
        private static Expression GenerateNotEqual(Expression left, Expression right)
        {
            if (UseCompareMethod(left, right))
            {
                return Expression.NotEqual(GenerateCompareMethod(left, right), Expression.Constant(0));
            }
            return Expression.NotEqual(left, right);
        }
        private static Expression GenerateProperty(Type type, string propertyPath, Expression instance)
        {
            Expression expression = instance;
            if (!string.IsNullOrEmpty(propertyPath))
            {
                string[] strArray = propertyPath.Split(new char[] { '.' });
                for (int i = 0; i < strArray.Length; i++)
                {
                    PropertyInfo property = type.GetProperty(strArray[i]);
                    if (property == null)
                    {
                        property = GetPropertyOrIndexer(type, propertyPath);

                        expression = Expression.Property(expression, property, new Expression[] { Expression.Constant(propertyPath) });
                    }
                    else
                    {
                        expression = Expression.Property(expression, property);
                    }
                    type = property.PropertyType;
                }
            }
            return expression;
        }
        private static Expression GenerateStartsWith(Expression left, Expression right)
        {
            return Expression.Equal(GenerateMethodCall("StartsWith", left, right), Expression.Constant(true));
        }
        private static Expression GenerateToLowerCall(Expression expression)
        {
            return Expression.Call(expression, GetParameterlessMethod("ToLower", expression));
        }
        private static bool UseCompareMethod(Expression left, Expression right)
        {
            if (UnwrapConversions(left).Type.IsEnum)
            {
                return false;
            }
            return ((left.Type == right.Type) && typeof(IComparable).IsAssignableFrom(left.Type));
        }
        private static Expression UnwrapConversions(Expression expr)
        {
            while (expr.NodeType == ExpressionType.Convert)
            {
                expr = ((UnaryExpression)expr).Operand;
            }
            return expr;
        }
        private static MethodInfo GetParameterlessMethod(string methodName, Expression expression)
        {
            return expression.Type.GetMethod(methodName, Type.EmptyTypes);
        }

        private static PropertyInfo GetPropertyOrIndexer(Type type, string propertyPath)
        {

            PropertyInfo indexer = null;
            foreach (PropertyInfo propertyInfo in type.GetProperties())
            {
                // We only support indexers with a single parameter.
                ParameterInfo[] parameterInfos = propertyInfo.GetIndexParameters();
                if (parameterInfos != null && parameterInfos.Length > 0)
                {

                    indexer = propertyInfo;
                    break;

                }

            }
            return indexer;
        }


        #endregion
    }
}
